#ifndef __RENDERERBASE__
#define __RENDERERBASE__

#include "..\Math\Point2.h"
#include "..\Schematics\Block.h"
#include "ITexture.h"
#include "BlockRenderers.h"
#include "..\Schematics\Schematic.h"
#include "RendererWorker.h"
#include "IRefresh.h"
#include "IProvideCulling.h"
#include "IProvideLighting.h"
#include "PrimitiveRendererBase.h"

#include <string>
#include <wx\stream.h>
#include <wx\thread.h>

/// <summary>
/// The base class for renderers.
/// </summary>
class RendererBase : public IProvideCulling, public IProvideLighting
{
	PrimitiveRendererBase **_textures;
	byte *_textureQueued;
	Schematic *_schematic;
	std::vector<IRefresh*> _refreshables;

	wxMutex _renderQueueMutex;
	std::vector<RendererWorker*> _workers;

protected:
	void CreateWorker();
	RendererWorker *GetMostFreeWorker();

public:
	virtual void Cleanup();

	void CreateWorkers()
	{
		int cores = wxThread::GetCPUCount();

		if (cores == -1)
			cores = 1;

		while (cores != 0)
		{
			CreateWorker();
			--cores;
		}
	}

	/// <summary>
	/// A back-reference to the block renderers for this renderer.
	/// </summary>
	BlockRenderers *Renderers;

	inline const std::vector<RendererWorker*> &GetWorkers() { return _workers; }

	int ChunksRendered;

	RendererBase() :
		Renderers(nullptr),
		_textures(nullptr),
		_textureQueued(nullptr),
		_schematic(nullptr),
		ChunksRendered(0)
	{
	};

	// IProvideCulling implementations
	bool CullObject(const Point3i &px);
	bool CullBlockSide(const RenderBox &box, const byte &faceSide);

	virtual ~RendererBase();

	inline PrimitiveRendererBase *GetChunkRender(const int &p) { return _textures[p]; }
	inline const byte &RenderQueueType(const int &p) { return _textureQueued[p]; }
	inline void SetRenderQueueType(const int &p, const byte &value) { _textureQueued[p] = value; }

	void ResetValues(bool reinit = true);
	void PreloadChunk(const int &x, const int &z);
	void PreloadChunks(const bool &boundsChanged = false);

	Schematic *GetSchematic() { return _schematic; }
	void SetSchematic(Schematic *schematic)
	{
		ResetValues(false);
		_schematic = schematic;
	}

	/// <summary>
	/// Translate the view matrix.
	/// </summary>
	/// <param name="x">X value.</param>
	/// <param name="y">Y value.</param>
	/// <param name="z">Z value.</param>
	virtual void Translate(const float &x, const float &y, const float &z) = 0;

	/// <summary>
	/// Rotate the view matrix.
	/// </summary>
	/// <param name="angle">Angle in degrees.</param>
	/// <param name="axisX">Whether the X axis is rotated or not.</param>
	/// <param name="axisY">Whether the Y axis is rotated or not.</param>
	/// <param name="axisZ">Whether the Z axis is rotated or not.</param>
	virtual void Rotate(const float &angle, const int &axisX, const int &axisY, const int &axisZ) = 0;

	/// <summary>
	/// Scale the view matrix.
	/// </summary>
	/// <param name="x">X scale.</param>
	/// <param name="y">Y scale.</param>
	/// <param name="z">Z scale.</param>
	virtual void Scale(const float &x, const float &y, const float &z) = 0;

	/// <summary>
	/// Save the current matrix state.
	/// </summary>
	virtual void PushState() = 0;

	/// <summary>
	/// Restore matching pushed matrix state.
	/// </summary>
	virtual void PopState() = 0;

	/// <summary>
	/// Setup the projection matrices
	/// </summary>
	virtual void SetupMatrices(const int &width, const int &height) = 0;

	/// <summary>
	/// Prepare 3D render matrices
	/// </summary>
	virtual void Setup3DRender() = 0;

	/// <summary>
	/// Prepare 2D render matrices
	/// </summary>
	virtual void Setup2DRender() = 0;

	/// <summary>
	/// Load or retrieve a texture file.
	/// </summary>
	/// <param name="str">The texture to load.</param>
	/// <returns>The texture file that was loaded. This will always be a valid texture file.</returns>
	ITexture *GetTexture(const std::string &str)
	{
		return Renderers->GetTexture(wxString(str));
	}

	/// <summary>
	/// Passes execution of a renderer to another block. You can use this to "forward" renders to different IDs.
	/// </summary>
	/// <param name="schematic">The current schematic being rendered.</param>
	/// <param name="block">The block to render.</param>
	/// <param name="position">The position of the block.</param>
	void ExecuteRenderer(PrimitiveRendererBase *data, Schematic *schematic, const Block &block, const Point3i &position)
	{
		Renderers->ExecuteRenderer(schematic, data, block, position);
	}

	bool IsFullBlockAt(const int &x, const int &y, const int &z)
	{
		return GetSchematic()->IsFullBlockAt(x, y, z);
	}

	bool IsOpaqueBlockAt(const int &x, const int &y, const int &z)
	{
		return GetSchematic()->IsOpaqueBlockAt(x, y, z);
	}

	inline bool IsFullBlockAt(const Point3i &p)
	{
		return IsFullBlockAt(p.X, p.Y, p.Z);
	}

	inline bool IsOpaqueBlockAt(const Point3i &p)
	{
		return IsOpaqueBlockAt(p.X, p.Y, p.Z);
	}

	/// <summary>
	/// Create a new, blank texture.
	/// </summary>
	/// <returns>The new texture.</returns>
	virtual ITexture *CreateTexture() = 0;

	/// <summary>
	/// Create an object which implements PrimitiveRendererBase for this renderer.
	/// </summary>
	/// <returns>The new primitive renderer.</returns>
	virtual PrimitiveRendererBase *CreatePrimitiveRenderer() = 0;

	/// <summary>
	/// Create the context on this control.
	/// </summary>
	/// <param name="control">The control to create the context on.</param>
	virtual void CreateContext(void *control, void *context) = 0;

	/// <summary>
	/// Make another control the current rendering context.
	/// </summary>
	/// <param name="control"></param>
	virtual void MakeCurrent(void *control, void *context) = 0;

	/// <summary>
	/// Swap the buffers.
	/// </summary>
	virtual void SwapBuffers(void *control) = 0;

	/// <summary>
	/// Prepare data required to render.
	/// </summary>
	virtual void PrepareRender() = 0;

	/// <summary>
	/// Sets the current texture pack.
	/// </summary>
	/// <param name="pack">The new pack file.</param>
	void SetTexturePack(const std::string &pack)
	{
		if (Renderers == nullptr)
			Renderers = new BlockRenderers(this, pack);
		else
			Renderers->SetTexturePack(pack);
	}

	void RegisterRefresher(IRefresh *refresh)
	{
		_refreshables.push_back(refresh);
	}

	void Refresh()
	{
		for (auto it = _refreshables.begin(); it != _refreshables.end(); ++it)
			(*it)->Refresh();
	}

	void RenderFinished(const int &pos, PrimitiveRendererBase *data)
	{
		ChunksRendered++;
		_textures[pos] = data;
		Refresh();
	}

	bool LightBoxSide(const struct RenderBox &box, const byte &faceIndex, const struct Face &face, Colorb vertexColors[4]);
};
#else
class RendererBase;
#endif